{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению. Сессия № 2\n",
    "Автор материала: программист-исследователь Mail.ru Group, старший преподаватель Факультета Компьютерных Наук ВШЭ Юрий Кашницкий. Материал распространяется на условиях лицензии [Creative Commons CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center>Тема 10. Бустинг\n",
    "## <center>Часть 8. Оценка результатов Xgboost"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Загрузка бибилиотек"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import xgboost as xgb\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.preprocessing import LabelEncoder"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Загрузка и подготовка данных\n",
    "\n",
    "Посмотрим на примере данных по оттоку клиентов из телеком-компании."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv(\"../../data/telecom_churn.csv\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Штаты просто занумеруем, а признаки International plan (наличие международного роуминга), Voice mail plan (наличие голосовой почтыы) и целевой Churn сделаем бинарными.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "state_enc = LabelEncoder()\n",
    "df[\"State\"] = state_enc.fit_transform(df[\"State\"])\n",
    "df[\"International plan\"] = (df[\"International plan\"] == \"Yes\").astype(\"int\")\n",
    "df[\"Voice mail plan\"] = (df[\"Voice mail plan\"] == \"Yes\").astype(\"int\")\n",
    "df[\"Churn\"] = (df[\"Churn\"]).astype(\"int\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Разделим данные на обучающую и тестовую выборки в отношении 7:3. Создадим соотв. объекты DMAtrix.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    df.drop(\"Churn\", axis=1), df[\"Churn\"], test_size=0.3, random_state=42\n",
    ")\n",
    "dtrain = xgb.DMatrix(X_train, y_train)\n",
    "dtest = xgb.DMatrix(X_test, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Зададим параметры Xgboost.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "params = {\"objective\": \"binary:logistic\", \"max_depth\": 3, \"silent\": 1, \"eta\": 0.5}\n",
    "\n",
    "num_rounds = 10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Будем отслеживать качество модели и на обучающей выборке, и на валидационной.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "watchlist = [(dtest, \"test\"), (dtrain, \"train\")]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Использование встроенных метрик \n",
    "В Xgboost реализованы большинство популярных метрик для классификации, регрессии и ранжирования:\n",
    "\n",
    "- `rmse` - [root mean square error](https://www.wikiwand.com/en/Root-mean-square_deviation)\n",
    "- `mae` - [mean absolute error](https://en.wikipedia.org/wiki/Mean_absolute_error?oldformat=true)\n",
    "- `logloss` - [negative log-likelihood](https://en.wikipedia.org/wiki/Likelihood_function?oldformat=true)\n",
    "- `error` (по умолчанию) - доля ошибок в бинарной классификации\n",
    "- `merror` - доля ошибок в классификации на несколько классов\n",
    "- `auc` - [area under curve](https://en.wikipedia.org/wiki/Receiver_operating_characteristic?oldformat=true)\n",
    "- `ndcg` - [normalized discounted cumulative gain](https://en.wikipedia.org/wiki/Discounted_cumulative_gain?oldformat=true)\n",
    "- `map` - [mean average precision](https://en.wikipedia.org/wiki/Information_retrieval?oldformat=true)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xgb_model = xgb.train(params, dtrain, num_rounds, watchlist)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Чтоб отслеживать log_loss, просто добавим ее в словарь params.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "params[\"eval_metric\"] = \"logloss\"\n",
    "xgb_model = xgb.train(params, dtrain, num_rounds, watchlist)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Можно отслеживать сразу несколько метрик.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "params[\"eval_metric\"] = [\"logloss\", \"auc\"]\n",
    "xgb_model = xgb.train(params, dtrain, num_rounds, watchlist)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Создание собственной метрики качества\n",
    "\n",
    "**Чтобы создать свою метрику качества, достаточно определить функцию, принимающую 2 аргумента: вектор предсказанных вероятностей и объект `DMatrix` с истинными метками.  \n",
    "В этом примере функция вернет просто число объектов, на которых классификатор ошибся, когла относил к классу 1 при превышении предсказанной вероятности класса 1 порога 0.5. \n",
    "Далее передаем эту функцию в xgb.train (параметр feval), если метрика тем лучше, чем меньше, надо дополнительно указать `maximize=False`.**\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# custom evaluation metric\n",
    "def misclassified(pred_probs, dmatrix):\n",
    "    labels = dmatrix.get_label()  # obtain true labels\n",
    "    preds = pred_probs > 0.5  # obtain predicted values\n",
    "    return \"misclassified\", np.sum(labels != preds)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xgb_model = xgb.train(\n",
    "    params, dtrain, num_rounds, watchlist, feval=misclassified, maximize=False\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**С помощью параметра evals_result можно сохранить значения метрик по итерациям.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "evals_result = {}\n",
    "xgb_model = xgb.train(\n",
    "    params,\n",
    "    dtrain,\n",
    "    num_rounds,\n",
    "    watchlist,\n",
    "    feval=misclassified,\n",
    "    maximize=False,\n",
    "    evals_result=evals_result,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "evals_result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ранняя остановка\n",
    "**Ранняя остановка используется для того, чтобы прекратить обучение модели, если ошибка за несколько итераций не уменьшилась.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "params[\"eval_metric\"] = \"error\"\n",
    "num_rounds = 1500\n",
    "\n",
    "xgb_model = xgb.train(params, dtrain, num_rounds, watchlist, early_stopping_rounds=10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Booster best train score: {}\".format(xgb_model.best_score))\n",
    "print(\"Booster best iteration: {}\".format(xgb_model.best_iteration))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Кросс-валидация с Xgboost\n",
    "**Продемонстрируем функцию xgboost.cv.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_rounds = 10\n",
    "hist = xgb.cv(params, dtrain, num_rounds, nfold=10, metrics={\"error\"}, seed=42)\n",
    "hist"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Замечания:\n",
    "\n",
    "- по умолчанию на выходе DataFrame (можно поменять параметр `as_pandas`),\n",
    "- метрики передатся как параметр (можно и несколько),\n",
    "- можно использовать и свои метрики (параметры `feval` и `maximize`),\n",
    "- можно также использовать раннюю остановку ( `early_stopping_rounds`)"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
